﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using AForge.Neuro;
using AForge.Neuro.Learning;

namespace Projekt1SN
{
    public class KohonenNetwork: INeuralNetwork
    {
        public DistanceNetwork network;
        private SOMLearning teacher;
        private int cyclesNo;
        private double nChangingRate;
        private double lChangingRate;
        private int neighborhoodSize;
        private double learningRate;
        private double[][] input;

        public KohonenNetwork(int inputsCount, int width, int height, int cyclesNo, int neighborhood, double nChangingRate, double learningRate, double lChangingRate)
        {
            network = new DistanceNetwork(inputsCount, width * height);
            teacher = new SOMLearning(network, width, height)
                          {
                              LearningRadius = neighborhood,
                              LearningRate = learningRate
                          };
            this.cyclesNo = cyclesNo;
            this.nChangingRate = nChangingRate;
            this.lChangingRate = lChangingRate;
            this.neighborhoodSize = neighborhood;
            this.learningRate = learningRate;
        }

        public void LoadTrainingData(string trainingData)
        {
            trainingData = trainingData.Trim();
            trainingData = trainingData.TrimEnd(']');
            string[] trainingStrings = trainingData.Split(new char[] { ']' }, int.MaxValue);

            input = new double[trainingStrings.Length][];

            for (int i = 0; i < input.Length; i++)
            {
                input[i] = new double[network.InputsCount];
            }

            for (int i = 0; i < trainingStrings.Length; i++)
            {
                string[] numbers = trainingStrings[i].Split(new char[] { ';' });

                for (int j = 0; j < numbers.Length; j++)
                {
                    numbers[j] = numbers[j].TrimStart('[', ';','\r','\n', ' ');
                    numbers[j] = numbers[j].TrimEnd(']', ';', '\r', ' ');
                    numbers[j] = numbers[j].Trim();
                    
                    numbers[j] = numbers[j].Replace('.', ',');
                    input[i][j] = double.Parse(numbers[j]);
                }               
            }
        }

        public void LoadInput(string intput)
        {
            LoadTrainingData(intput);
        }

        public void Learn(bool showErrors, bool saveToFile)
        {
            System.IO.StreamWriter file = new System.IO.StreamWriter("leariningLog.txt"); ;
            for (int i = 0; i < cyclesNo; i++)
            {
                double error = teacher.RunEpoch(input);
                
                //teacher.LearningRate = learningRate*lChangingRate*(-1/((neighborhoodSize*nChangingRate) + 1));
                teacher.LearningRadius *= nChangingRate;
                teacher.LearningRate *= lChangingRate;
                if (showErrors)
                {
                    Console.WriteLine("Iteration: {0} squared error value: {1}", i, error / input.Length);
                }
                if (saveToFile)
                {
                    file.WriteLine("Iteration: {0} squared error value: {1}", i, error/input.Length);
                }
            }
            file.Close();
        }

        public double[][] ShowInput()
        {
            return input;
        }

        public double[][] Run(double[][] input)
        {
            List<double[]> outputList = new List<double[]>();

            for (int i = 0; i < input.Length; i++)
            {
                network.Compute(input[i]);
                int winner = network.GetWinner();
                double[] output = new double[network[0][winner].InputsCount];

                for (int j = 0; j < output.Length; j++)
                {
                    output[j] = network[0][winner][j];
                }
                
                outputList.Add(output);
            }
            return outputList.ToArray();
        }

        public double[][] Run()
        {
            return Run((input));
        }

        public void PrintInputOutput(double[][] input, double[][] output)
        {
            for (int i = 0; i < input.Length; i++)
            {
                Console.WriteLine("\nInput \t\t\t Output:\n");

                for (int j = 0; j < input[i].Length; j++)
                {
                    Console.WriteLine("{0} \t\t {1}", input[i][j].ToString(" 0.0000"), output[i][j].ToString("0.0000"));
                }
            }
        }

        public void PrintWeights()
        {
            for (int i = 0; i < network.LayersCount; i++)
            {
                for (int j = 0; j < network[i].NeuronsCount; j++)
                {
                    for (int k = 0; k < network[i][j].InputsCount; k++)
                    {
                        Console.WriteLine("Layer: {0}  Neuron: {1}  WeightNumber: {2}  WeightValue: {3}", i, j, k, network[i][j][k]);
                    }
                }
            }
        }
    }
}
